package com.romotc.qqrobot.webqq.qqInterfaceCatching;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import com.romotc.scache.RequestSpy;
import com.romotc.scache.WebConnection;


/**
 * 发出请求信息 并拦截修改服务器返回的信息
 * @author Administrator
 *
 */
public class QQUrlBodyModifyCatching extends QQUrlCatchingInterface {

	@Override
	public boolean hackRequest(RequestSpy requestSpy) {
		return true;
	}


	/**
	 * 对获取好友返回的信息进行修改
	 * 增加陌生人客户（当前QQ上面未加该客户）
	 * 根据客户资料 重建好友分组
	 */
	public final boolean modifyResponse(WebConnection wc ,RequestSpy rqSpy) throws IOException{
		
		//获取服务器返回的信息
		String s = getResponseString(wc, rqSpy);
		
		//获取修改后的响应体
		String afterModifyString = modifyResponseBody(s, rqSpy);
		
		//替换流
		byte[] respon = afterModifyString.getBytes(rqSpy.resCharSet);
		wc.webis = new DataInputStream(new ByteArrayInputStream(respon));
		
		//修改响应体长度
		Map<String, String> h1 = new HashMap<String, String>();
		int len = respon.length;
		h1.put("Content-Length", String.valueOf(len));
		rqSpy.headers = modifyResHeaders(rqSpy.headers,h1);
		//修改响应体长度
		rqSpy.ctsize = len;
		
		if(s != null )
			return s.equals(afterModifyString);
		else if(afterModifyString != null)
			return afterModifyString.equals(s);
		return false;
	}
	
	
	/**
	 * 修改服务器响应体
	 * @param s
	 * @return
	 */
	public String modifyResponseBody(String origResPonseBody, RequestSpy rqSpy)
	{
		return origResPonseBody;
	}
	
	
	
	/**
	 * 返回请求响应体
	 * @param wc
	 * @param rqSpy
	 * @return
	 * @throws IOException
	 */
	public final String getResponseString(WebConnection wc ,RequestSpy rqSpy) throws IOException{
		
		ByteArrayOutputStream bos = getResponseOutPutStream(wc, rqSpy);
		
		String s = new String(bos.toByteArray(), rqSpy.resCharSet);
		int len1 = s.getBytes(rqSpy.resCharSet).length;
		rqSpy.ctsize = len1;
		
		return s;
	}
	
	
	
	/**
	 * 读取服务器返回的信息
	 * @param wc
	 * @param rqSpy
	 * @return
	 * @throws IOException
	 */
	public final ByteArrayOutputStream getResponseOutPutStream(WebConnection wc ,RequestSpy rqSpy) 
		throws IOException{
		
		// 读服务器返回的信息 从request_trans_object摘取
		int read = 0;
		int i;
		long whenaborted;
		boolean clientdead;
		boolean filedead;
		DataInputStream sin = wc.webis;
		
		//将请求的响应内容暂存在这里
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		DataOutputStream out = new DataOutputStream(bos);
		
		byte b[] = new byte[4096];
		clientdead = filedead = false;
		whenaborted = 0;
		int todo;
		if (wc.socket != null)
			wc.socket.setSoTimeout(rqSpy.read_timeout);
		
		
		
		boolean chunked = rqSpy.chunked;
//		int ctsize = rqSpy.ctsize;
		int log_size = rqSpy.log_size;
		int qa_minlen = rqSpy.qa_minlen;
		
		if (chunked == true)
			rqSpy.ctsize = -1; // drop ctsize on chunked streams
		if (rqSpy.ctsize < 0) {
			todo = Integer.MAX_VALUE; // read all input data
		} else
			todo = rqSpy.ctsize;
		
		
		chunk: while (true) {
			if (chunked) {
				// this small part of code is from Rabbit2 HttpInputStream code
				// getChunkSize() function.
				// Copyright (c) 1998-2002 Robert Olofsson and others
				/* get a chunk size from http stream */
				String line;
				line = sin.readLine();
				if (line == null)
					throw new IOException("EOF when reading chunk block size");
				StringTokenizer st = new StringTokenizer(line, "\t \n\r(;");
				if (st.hasMoreTokens()) {
					String hex = st.nextToken();
					try {
						todo = Integer.parseInt(hex, 16);
					} catch (NumberFormatException e) {
						throw new IOException(
								"Chunk size is not a hex number: '" + line
										+ "', '" + hex + "'.");
					}
				} else {
					throw new IOException("Chunk size is not available: '"
							+ line + "'");
				}
			}
			if (todo == 0 && chunked) {
				String line;
				// skip Trailer
				while ((line = sin.readLine()) != null) {
					if (line.length() == 0)
						break;
				}
				break chunk;

			}
			while (todo > 0) {
				try {
					/* precist ze serveru */
					i = sin.read(b, 0, Math.min(todo, 4096));
				} catch (IOException e) {
					/* chyba pri cteni dat ze serveru */
//					if (file != null)
//						file.close();
					rqSpy.ctsize = read;
					throw e;
				}
				if (i == -1) {
					wc.keepalive = false;
					break; /* end of Data stream? */
				}

				todo -= i;
				read += i;

				/* zapsat do diskoveho souboru 
				if (file != null) {
					dir.dirty = true;
					try {
						file.write(b, 0, i);
						if (read > maxcacheable)
							throw new EOFException("Data too long");
					} catch (IOException writerr) {
						try {
							file.close();
						} catch (IOException ignore) {
						} finally {
							file = null;
							filedead = true;
						}
					}
				}*/

				/* zapisovat do klienta opatrne! */
				try {
					if (!clientdead) {
						out.write(b, 0, i);
						log_size += i;
					} else if (qa_minlen < 0
							&& System.currentTimeMillis() - whenaborted > rqSpy.qa_maxtime) {
						rqSpy.ctsize = read;
						wc.close();
						throw new IOException("Aborted transfer time timed out");
					}
				} catch (IOException e) {
//					if (file == null) /* not caching */
//					{
//						ctsize = read;
//						wc.close();
//						throw e;
//					}
					if (CheckQuickAbort(read, rqSpy.cacheable, rqSpy.trace_abort,
							qa_minlen, rqSpy.getURL(), rqSpy.ctsize, rqSpy.qa_maxlen, rqSpy.qa_percent)) {
						rqSpy.ctsize = read;
//						file.close();
						wc.close();
						throw e;
					}
					clientdead = true;
					whenaborted = System.currentTimeMillis();
				}
			}
			// end off server input data
			if (!chunked)
				break;
			sin.readLine(); // read CRLF
			
			
		}
		return bos;
	}
	
	
	public final ArrayList<String> modifyResHeaders(ArrayList<String> headers, Map<String, String> headerReplaceTo) {
		if(headerReplaceTo != null && headerReplaceTo.size() > 0) {
			Map<String, String> headersMap = new HashMap<String, String>();
			String key;
			String value;
			if(headers != null && headers.size() > 0) {
				int idx;
				for(String header : headers) {
					idx = header.indexOf(":");
					key = header.substring(0, idx);
					value = header.substring(idx + 1);
					headersMap.put(key, value.trim());
				}
				
			}
			
			for(Map.Entry<String, String> entry : headerReplaceTo.entrySet()) {
				headersMap.put(entry.getKey(), entry.getValue());
			}
			
			ArrayList<String> newHeaders = new ArrayList<String>();
			for(Map.Entry<String, String> entry : headersMap.entrySet()) {
				newHeaders.add(entry.getKey() + ": " + entry.getValue());
			}
			
			return newHeaders;
		}
		
		return headers;
	}
	
	
	
	
	/* return true if the request should be aborted */
	final private boolean CheckQuickAbort(int readb, boolean cacheable,
			boolean trace_abort, int qa_minlen, String URL, int ctsize, int qa_maxlen,
			float qa_percent) {
		if (!cacheable) {
			if (trace_abort == true)
				System.out.println("[TRACE " + Thread.currentThread().getName()
						+ "] # Load aborted (non-cacheable): " + URL);
			return true;
		}

		if (qa_minlen < 0) {
			/* disabled */
			if (trace_abort == true)
				System.out.println("[TRACE " + Thread.currentThread().getName()
						+ "] # Load continued (quick abort disabled): " + URL);
			return false;
		}

		if (ctsize != -1) /* Content-size is known */
		{
			if (readb > ctsize) {
				if (trace_abort == true)
					System.out
							.println("[TRACE "
									+ Thread.currentThread().getName()
									+ "] # Load aborted (loaded more data than expected): "
									+ URL);
				return true;
			}

			if ((ctsize - readb) < qa_minlen) {
				/* only little bytes left */
				if (trace_abort == true)
					System.out
							.println("[TRACE "
									+ Thread.currentThread().getName()
									+ "] # Load continued (less than minlen bytes left): "
									+ URL);
				return false;
			}
			if ((ctsize - readb) > qa_maxlen) {
				/* too much left to go */
				if (trace_abort == true)
					System.out.println("[TRACE "
							+ Thread.currentThread().getName()
							+ "] # Load aborted (more than max bytes left): "
							+ URL);
				return true;
			}
			if ((float) readb / ctsize > qa_percent) {
				/* past point of no return */
				if (trace_abort == true)
					System.out.println("[TRACE "
							+ Thread.currentThread().getName()
							+ "] # Load continued (percent ratio is good): "
							+ URL);
				return false;
			} else {
				if (trace_abort == true)
					System.out
							.println("[TRACE "
									+ Thread.currentThread().getName()
									+ "] # Load aborted (percent ratio is bad): "
									+ URL);
				return true;
			}
		}

		if (trace_abort == true)
			System.out.println("[TRACE " + Thread.currentThread().getName()
					+ "] # Load aborted (unspecified size in reply): " + URL);
		return true; /* STOP DOWNLOADING! */

		/*
		 * return false; no dobra povolime to, ale vypadne to na timeout nebo na
		 * pocet dat
		 */
	}

}
